#include <iostream>
#include <fstream>

#include <string> 
#include <algorithm>
#include <sstream>
#include "./wavio/AudioFile.hpp"

#include "Gist.h"
#include"signalconditioning/Framer.h"
#include"onset-detection-functions/onSets.h"
#include "chroma/FeatureExtractor.h"
using namespace std;
typedef double Real;
struct audioFeature
{
	vector<Real> beats;
	vector<string> beatsId;
	vector< vector<Real> > beatChroma;
	vector<Real> energy;
};

// global variable
  
int sampleRate = 44100;
int frameSize = 1024;
int hopSize = 512;
bool write_audio=true;
int max_f = 5000;
int min_f = 40;
/* audio load function
input : file name(string);
output:  audio signal (vector<Real>)
 */
// returns the sum of the squared array = the energy of the array
template <typename T> T energy(const std::vector<T>& array) {
	return inner_product(array.begin(), array.end(), array.begin(), (T)0.0);
}

// returns the instantaneous power of an array
template <typename T> T instantPower(const std::vector<T>& array) {
	return energy(array) / array.size();
}

// returns the mean of frames
template <typename T>
std::vector<T> meanFrames(const std::vector<std::vector<T> >& frames, int beginIdx = 0, int endIdx = -1) {


	if (endIdx == -1) endIdx = (int)frames.size();
	unsigned int vsize = frames[0].size();

	std::vector<T> result(vsize, (T)0.0);
	typename std::vector<std::vector<T> >::const_iterator it = frames.begin() + beginIdx;
	typename std::vector<std::vector<T> >::const_iterator end = frames.begin() + endIdx;
	for (; it != end; ++it) {
		typename std::vector<T>::const_iterator itFrame = it->begin();
		typename std::vector<T>::const_iterator endFrame = it->end();
		typename std::vector<T>::iterator itResult = result.begin();
		for (; itFrame != endFrame; ++itFrame, ++itResult) {
			*itResult += *itFrame;
		}
	}
	for (unsigned int j = 0; j<vsize; j++) result[j] /= (endIdx - beginIdx);

	return result;
}
int detect_leading_silence(vector<Real>  &audio, Real silence_threshold,int chunk_size)
{
	vector<Real>  windAudio_s;
	unsigned int begin=0;
		
	while(begin+chunk_size<audio.size())
	{
		windAudio_s=vector<Real>(audio.begin()+begin, audio.begin()+begin+chunk_size);
		//cout<<instantPower(windAudio_s)<<endl;
		if(instantPower(windAudio_s)>silence_threshold)
		{
			break;
		}
		begin+=chunk_size;
	}
	return begin;
}
void audioSilenceRemoval(vector<Real>  &audio)
{
  if(audio.size()<=2205) 
  	return;
  int begin=detect_leading_silence(audio, 0.0002, 2205);

  audio=vector<Real>(audio.begin()+begin, audio.end());

  vector<Real> bk= audio;
  reverse(bk.begin(),bk.end());  
  int end = detect_leading_silence(bk, 0.0002, 2205);

  vector<Real> windAudio_s=vector<Real>(audio.begin(), audio.end()-end-1);

  audio=windAudio_s;
}
bool  audioLoader(string fileName, int sampleRate , vector<Real>  &audio )
{
  AudioFile<Real> audioFile;
  bool suc=audioFile.load(fileName);
  if(!suc)
  {
	  cout<<"file read error"<<endl;
	  return false;
  }
  int sr = audioFile.getSampleRate();
  if(sr!=sampleRate)
  {
	  cout<<"sampleRate should be 44100"<<endl;
	  return false;
  }
  audio = audioFile.samples[0];
  /*int begin=detect_leading_silence(audio, 0.0001, 4410);
  vector<Real> bk= audio;
  reverse(bk.begin(),bk.end());  
  int end = detect_leading_silence(bk, 0.0001, 4410);
  vector<Real> windAudio_s=vector<Real>(audio.begin()+begin, audio.end()-end-1);
  if(write_audio)
  {
	AudioFile<Real>::AudioBuffer buffer;

	buffer.resize(1);
	buffer[0] = windAudio_s;

	audioFile.setAudioBufferSize(1, windAudio_s.size());
	audioFile.setBitDepth(16);
	audioFile.setSampleRate(44100);
	bool ok = audioFile.setAudioBuffer(buffer);
	audioFile.save("sil.wav");
  }
  audio=windAudio_s;*/
  return true;
}

void beatDetectionByProfile(string &profile, vector<string> &beatsId, vector<Real> &beats, vector< vector<Real> > &chromas, vector<Real> &audio)
{
	ifstream infile(profile.c_str());
	if (infile.is_open())
	{
		string id;
		Real beat_begin;
		Real beat_end;
		char c;
		Real beat_pre = 0;

		string str;
		string mid;
		while (infile >> str)
		{
			stringstream ss(str);
			getline(ss, id, ':');
			beatsId.push_back(id);
			getline(ss, mid, ',');
			getline(ss, mid);
			beat_end = strtof(mid.c_str(), NULL);
			beats.push_back(beat_end / 1000);
		}
	}
	infile.close();

	Framer framer;
	framer.configure(frameSize, hopSize);
	framer.setSource(&audio[0], audio.size());
	unsigned int	m_maxFrames = framer.getMaxNoFrames();
	unsigned int frameIdx = 0;
	vector<Real> audioFrame(frameSize, 0);
	Gist<Real> gist(frameSize, sampleRate);


	FeatureExtractor::Parameters params{ float(sampleRate) };
	params.fftSize = frameSize;
	params.useChromaFrequencyMap = true;
	params.minFrequency = min_f;
	params.maxFrequency = max_f;
	FeatureExtractor fe(params);
	while (frameIdx<m_maxFrames)
	{
		framer.getFrame(&audioFrame[0]);
		gist.processAudioFrame(audioFrame);

		vector<double> chroma = fe.process(gist.fftReal, gist.fftImag);
		chromas.push_back(chroma);

		frameIdx++;
	}
}
void beatsDetection(vector<Real> &audio, vector<Real> &beats, vector<string> &beatsId, vector< vector<Real> > &chromas, vector<int> &onsetsIdx)
{
	Framer framer;
	framer.configure(frameSize, hopSize);
	framer.setSource(&audio[0], audio.size());
	unsigned int	m_maxFrames = framer.getMaxNoFrames();
	unsigned int frameIdx = 0;
	vector<Real> audioFrame(frameSize, 0);
	Gist<Real> gist(frameSize, sampleRate);

	FeatureExtractor::Parameters params{ float(sampleRate) };
	params.fftSize = frameSize;
	params.useChromaFrequencyMap = true;
	params.minFrequency = min_f;
	params.maxFrequency = max_f;
	FeatureExtractor fe(params);
	vector<Real> dfs;


	while (frameIdx<m_maxFrames)
	{
		framer.getFrame(&audioFrame[0]);
		gist.processAudioFrame(audioFrame);
		float hfc = gist.highFrequencyContent();
		dfs.push_back(hfc);
		vector<double> chroma = fe.process(gist.fftReal, gist.fftImag);
		chromas.push_back(chroma);

		frameIdx++;
	}
	vector<double> onsets = Onsets(dfs, sampleRate, frameSize, hopSize, 0, onsetsIdx);
	cout << onsets.size() << endl;
	beats = vector<Real>(onsets.begin() + 1, onsets.end());
	beats.push_back(double(audio.size()) / sampleRate);
	for (unsigned int i = 0; i<beats.size(); i++)
	{

		std::stringstream ss;
		std::string str;
		ss << i;
		ss >> str;
		beatsId.push_back(str);
	}

}




/* beat feature extration
input : signal of one beat (vector<Real>)
output: beat feature (vector<Real>)
 */
void beatFeatureExtration(vector< vector<Real> >& chromas,Real begin, Real end, vector<Real>& beatFeature)
{

	begin = ceil(begin*sampleRate / hopSize)>chromas.size() - 1 ? chromas.size() - 1 : ceil(begin*sampleRate / hopSize);
	end = ceil(end*sampleRate / hopSize)>chromas.size() - 1 ? chromas.size() - 1 : ceil(end*sampleRate / hopSize);

  
    beatFeature= meanFrames(chromas,begin,end);

}

void audioFeatureExtraction(string &audioFile, string &audioProfile, audioFeature &featureStandard)
{

	  vector<Real>  audio_s;
	  // load audio
	  audioLoader(audioFile,sampleRate ,audio_s);
	  vector< vector<Real> > chromas;
	  // load the onsets
	  beatDetectionByProfile(audioProfile,featureStandard.beatsId, featureStandard.beats,chromas,audio_s);

	  //beatsDetection(audio_s,featureStandard.beats, featureStandard.beatsId, factory);
	  // compute the feature
	  vector<Real>  beatAudio_s;
	  vector<Real> beats_s(featureStandard.beats);

	  for (unsigned int i=0; i<beats_s.size();i++)
	  {
	  	beatAudio_s.clear();
	  	int begin=0; int end=0;
	  	if(i==0) { begin=0; end=int(beats_s[i]*sampleRate); }
	  	else 
	  	{
	  	  begin=int(beats_s[i-1]*sampleRate);
	  	  end=int(beats_s[i]*sampleRate);
	  	}
	  	copy ( audio_s.begin() + begin, audio_s.begin() + end-1, std::back_inserter(beatAudio_s) );
	  	vector<Real> beatFeature;
	  	//cout<< instantPower(beatAudio_s)<<endl;
	  	//audioSilenceRemoval(beatAudio_s);// remove silence
		beatFeatureExtration(chromas, i == 0 ? 0 : beats_s[i - 1], beats_s[i], beatFeature);

	  	featureStandard.beatChroma.push_back(beatFeature);
		featureStandard.energy.push_back(instantPower(beatAudio_s));
	  	stringstream ss;
		if(write_audio)
		{
			ss << featureStandard.beatsId[i];
			string str = ss.str();
			string filename="beat_"+str+".wav";
			AudioFile<Real> audioFile;
			AudioFile<Real>::AudioBuffer buffer;

			buffer.resize(1);
			buffer[0] = beatAudio_s;

			audioFile.setAudioBufferSize(1, beatAudio_s.size());
			audioFile.setBitDepth(16);
			audioFile.setSampleRate(44100);
			bool ok = audioFile.setAudioBuffer(buffer);
			audioFile.save(filename);
	  	}
	  }
}

void dumpAudioFeatureToFile(string &audioFile, string &audioProfile, string &outfile)
{
	audioFeature featureStandard;
	audioFeatureExtraction(audioFile,audioProfile,featureStandard);
	ofstream ofile;
	ofile.open(outfile.c_str());
	for (unsigned int i=0; i<featureStandard.beatsId.size();i++)
	{
		ofile<<featureStandard.beatsId[i]<<",";
		ofile<<featureStandard.beats[i]<<",";
		ofile<<featureStandard.beatChroma[i][0]<<",";
		ofile<<featureStandard.beatChroma[i][1]<<",";
		ofile<<featureStandard.beatChroma[i][2]<<",";
		ofile<<featureStandard.beatChroma[i][3]<<",";
		ofile<<featureStandard.beatChroma[i][4]<<",";
		ofile<<featureStandard.beatChroma[i][5]<<",";
		ofile<<featureStandard.beatChroma[i][6]<<",";
		ofile<<featureStandard.beatChroma[i][7]<<",";
		ofile<<featureStandard.beatChroma[i][8]<<",";
		ofile<<featureStandard.beatChroma[i][9]<<",";
		ofile<<featureStandard.beatChroma[i][10]<<",";
		ofile<<featureStandard.beatChroma[i][11]<<",";
		ofile<<featureStandard.energy[i]<<endl;
	}
	ofile.close();
}